package com.geekhalo.lego.core.query.support;

import com.geekhalo.lego.core.query.NoQueryApplicationService;
import com.geekhalo.lego.core.query.QueryService;
import com.geekhalo.lego.core.query.QueryServicesRegistry;
import com.geekhalo.lego.core.support.intercepter.MethodDispatcherInterceptor;
import com.geekhalo.lego.core.support.invoker.QueryServiceBasedServiceMethodInvoker;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.slf4j.Logger;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Method;
import java.util.Set;

/**
 * Created by taoli on 2022/9/25.
 * gitee : https://gitee.com/litao851025/lego
 * 编程就像玩 Lego
 */
@Slf4j
public class QueryApplicationServiceProxyFactory
    extends BaseQueryProxyFactory {

    @Autowired
    private QueryServicesRegistry queryServicesRegistry;


    public <T> T createQueryApplicationService(Class<T> queryService){
        return super.createProxy(queryService);
    }


    @Override
    protected Logger getLogger() {
        return log;
    }

    @Override
    protected QueryServiceMetadata buildMetadata(Class queryService) {
        return QueryServiceMetadata.fromQueryApplicationService(queryService);
    }

    @Override
    protected Class getSkipAnnotation() {
        return NoQueryApplicationService.class;
    }

    @Override
    protected <T> void beforeRegisterInterceptors(Class<T> queryService,
                                            Set<Method> methods,
                                            Object repository,
                                            QueryServiceMetadata metadata,
                                            ProxyFactory result) {
        MethodDispatcherInterceptor queryServiceMethodDispatcherInterceptor = createQueryServiceMethodDispatcherInterceptor(methods, repository, metadata);
        log.info("Auto Register QueryServiceMethod for {} : {}", queryService, queryServiceMethodDispatcherInterceptor);
        result.addAdvice(queryServiceMethodDispatcherInterceptor);
    }

    private MethodDispatcherInterceptor createQueryServiceMethodDispatcherInterceptor(Set<Method> methods, Object repository, QueryServiceMetadata metadata) {
        MethodDispatcherInterceptor methodDispatcher = new MethodDispatcherInterceptor();
        Set<Method> methodsForRemove = Sets.newHashSet();
        for (Method method : methods) {
            if (methodsForRemove.contains(method)){
                continue;
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1){
                continue;
            }
            Class parameterType = parameterTypes[0];
            Class resultType = method.getReturnType();

            QueryService queryServiceToUse = null;

            for (QueryService queryService : this.queryServicesRegistry.getQueryServices()){
                Method executeMethod = MethodUtils.getMatchingMethod(queryService.getClass(), QueryService.METHOD_NAME, parameterType);
                if (executeMethod != null && executeMethod.getReturnType() == resultType){
                    queryServiceToUse = queryService;
                    break;
                }
            }
            if (queryServiceToUse != null){
                Set<Method> overrideHierarchy = MethodUtils.getOverrideHierarchy(method, ClassUtils.Interfaces.INCLUDE);
                for (Method override : overrideHierarchy){
                    methodDispatcher.register(override, new QueryServiceBasedServiceMethodInvoker(queryServiceToUse));
                    methodsForRemove.add(override);
                }
            }
        }
        methods.removeAll(methodsForRemove);
        return methodDispatcher;
    }
}
